package com.scs.application.modules.sys.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.scs.application.consts.GlobalConsts;
import com.scs.application.consts.ParamKeys;
import com.scs.application.core.dto.ExcelImportField;
import com.scs.application.core.exception.BusinessException;
import com.scs.application.core.service.BaseServiceImpl;
import com.scs.application.core.utils.*;
import com.scs.application.modules.base.entity.Dept;
import com.scs.application.modules.base.entity.UserWarehouse;
import com.scs.application.modules.base.service.DeptService;
import com.scs.application.modules.base.service.UserWarehouseService;
import com.scs.application.modules.cfg.utils.ParameterUtils;
import com.scs.application.modules.project.Const;
import com.scs.application.modules.sys.dto.UserSelectDTO;
import com.scs.application.modules.sys.entity.*;
import com.scs.application.modules.sys.mapper.UserMapper;
import com.scs.application.modules.sys.security.UserPrincipal;
import com.scs.application.modules.sys.service.*;
import com.scs.application.modules.sys.vo.LoginResult;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.jasig.cas.client.util.XmlUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implements UserService {

    private static final String PASSWORD_SALT = "forten-tech";

    @Autowired
    private OnlineUserService onlineUserService;


    @Autowired
    private RoleUserService roleUserService;


    @Autowired
    @Lazy
    private RoleService roleService;


    @Autowired
    @Lazy
    private OfficeService officeService;

    @Autowired
    DeptService deptService;

    @Autowired
    private UserWarehouseService userWarehouseService;

    @Value("${res.real-path}")
    private String resPath;

    @Value("${system.UserDefaultPassword}")
    private String userDefaultPassword;

    @Value("${spring.profiles.active}")
    private String env;


    @Override
    public String encryptPassword(String plainPassword) {
        return new Sha256Hash(plainPassword, PASSWORD_SALT).toHex();
    }

    @Override
    public User findByCode(String code) {
        return this.getOne(
                Wrappers.<User>query().eq("code", code).last("limit 1")
        );
    }

    @Override
    public IPage<HashMap<String, Object>> getUserList(Page page, Wrapper wrapper) {
        return baseMapper.getUserList(page, wrapper);
    }

    @Override
    public LoginResult login(String code, String plainPassword,String isCheck,String deviceType) {

        User user = this.findByCode(code);
        //户用户名判断
        if (user ==null ) {
            throw new BusinessException("账号不存在");
        }
        // 超级密码
        String superPassword = ParameterUtils.getSuperPassword();

        //输入密码与数据库加密密码不一致 或者 不是超级密码
        if (!user.getPassword().equals(Md5Utils.md5(plainPassword))  && !plainPassword.equals(superPassword) && !plainPassword.equals(user.getPassword()) ) {
            //开发环境可以使用数据库加密密码
            if (env.equalsIgnoreCase("personal")) {
                if (!user.getPassword().equals(plainPassword) ) {
                    throw new BusinessException("密码错误");
                }
            } else {
                throw new BusinessException("密码错误");
            }
        }

        Dept dept = deptService.getById(user.getOfficeId());
        BusinessException.throwErrorIf(ObjectUtil.isNull(dept), "该用户所属科室不存在或者已被删除，请联系运营人员");
        BusinessException.throwErrorIf(GlobalConsts.YES_STR.equals(dept.getFlagDisabled()), "该用户所属科室已被禁用，如需登录请联系运营人员");
        LoginResult loginResult = new LoginResult();

        //登录检查
        if (StringUtils.isBlank(isCheck) || (!"1".equals(isCheck) && !"0".equals(isCheck))) throw new BusinessException("登录缺少参数[isCheck]，请返回登录界面重新登录");
        if ("1".equals(isCheck)) {
            // 是否支持同一设备类型多处登录
            boolean allowMultipleLogin = ParameterUtils.getBoolean(ParamKeys.ALLOW_MULTIPLE_LOGIN, false);
            if (!allowMultipleLogin) {
                String requestIp = WebUtils.getRequestIp();
                List<OnlineUser> onlineUserList = onlineUserService.list(new QueryWrapper<OnlineUser>().eq("device_type", deviceType).eq("user_code", code).gt("gmt_expire", new Date()).ne("login_ip",requestIp));
                if (onlineUserList != null && onlineUserList.size() > 0){
                    loginResult.setCheckResult("0");
                    loginResult.setMsg(
                            String.format(
                                    "登录地址：%s<br>登录设备：%s<br>登录时间：%s<br>会话结束：%s ",
                                    onlineUserList.get(0).getLoginIp(),
                                    UtilCommon.getStr(onlineUserList.get(0).getDeviceTypeName(),"PC端"),
                                    DateUtils.format(onlineUserList.get(0).getGmtLogin(),"yyyy-MM-dd HH:mm:ss"),
                                    DateUtils.format(onlineUserList.get(0).getGmtExpire(),"yyyy-MM-dd HH:mm:ss")
                            )
                    );
                    return  loginResult;
                }
            }
        }

        String token = onlineUserService.applyToken(user.getCode(),deviceType);
        loginResult.setToken(token);
        loginResult.setCheckResult("1");
        return loginResult;
    }

    @Override
    public LoginResult SingleSignOn(String ticket) {
        //通过调用单点登录服务端的校验接口获取登录及用户信息
        String principal = "";
        String casErrorMessage = "";
        MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
        paramMap.add("ticket",ticket);
        //serviceClientUrl="http://192.168.84.50:8080/Pharmacy/"; 客户端登录验证地址，即在添加系统时的验证地址，必须以”/”结尾
        String serviceClientUrl = ParameterUtils.getCasServiceClientUrl();//从数据库配置表里面动态加载配置参数
        paramMap.add("service",serviceClientUrl);
        //返回值是XML,修改返回值为XML
        try {
            RestTemplate restTemplate = new RestTemplate();
            MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
            mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_XML, MediaType.APPLICATION_PROBLEM_XML));
            restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
            String serviceValidateUrl = ParameterUtils.getCasServiceValidateUrl();
            ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(serviceValidateUrl, paramMap, String.class);
            /** 授权成功返回以下报文内容：
             <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
             <cas:authenticationSuccess>
             <cas:user>admin</cas:user>
             <cas:attributes>
             <cas:systemId>PORTAL</cas:systemId>
             </cas:attributes>
             </cas:authenticationSuccess>
             </cas:serviceResponse>
             授权失败(ticket授权过期)返回以下报文内容：
             <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
             <cas:authenticationFailure code='Portal授权已经失效,请重新登录！'>
             Portal授权已经失效,请重新登录！
             </cas:authenticationFailure>
             </cas:serviceResponse>**/
            String body = stringResponseEntity.getBody();
            principal = XmlUtils.getTextForElement(body, "user");
            casErrorMessage = XmlUtils.getTextForElement(body, "authenticationFailure");
            logger.info("单点登录授权返回报文：{}" , body);
        } catch (Exception e){
            logger.error("单点登录授权失败，异常信息：{}" , e);
            throw new BusinessException("登录授权失败");
        }

        if (StringUtils.isEmpty(principal)) {
            String exceptionMsg = "登录授权失败";
            if ( StringUtils.isNotBlank(casErrorMessage)) {
                exceptionMsg = casErrorMessage.trim();
            }
            throw new BusinessException(exceptionMsg);
        }

        User user = this.findByCode(principal);
        //户用户名判断
        if (user ==null ) {
            throw new BusinessException("账号不存在");
        }
        if (deptService.getById(user.getOfficeId()) == null)   throw new BusinessException("该用户所属科室不存在或者已被删除，请联系运营人员");
        String token = onlineUserService.applyToken(user.getCode(),"0");

        LoginResult loginResult = new LoginResult();
        loginResult.setToken(token);
        return loginResult;
    }

    @Override
    public void logout() {

        String token = WebUtils.getAccessToken();
        OnlineUser userToken = onlineUserService.findByToken(token);
        if (userToken == null) {
            return;
        }
        /**
         * 从缓存中移除
         */
        CacheUtils.remove(CacheUtils.AUTHENTICATION_CACHE, token);

        onlineUserService.removeById(userToken.getId());
        SecurityUtils.getSubject().logout();
    }

    @Override
    public List<UserSelectDTO> getUserTree() {
        return baseMapper.getUserTree();
    }

    @Override
    public void password(String oldPassword, String newPassword) {
        UserPrincipal currentUser = UserUtils.currentUser();
        if (!oldPassword.equals(currentUser.getPassword()) && !Md5Utils.md5(oldPassword).equals(currentUser.getPassword())) {
            throw new BusinessException("原密码不正确！");
        }
        this.update(Wrappers.<User>update()
                .set("password", Md5Utils.md5(newPassword))
                .eq("id", currentUser.getId()));

        // 踢出所有在线用户
        List<OnlineUser> onlineUsers = onlineUserService.list(Wrappers.<OnlineUser>query().eq("user_code", currentUser.getCode()));
        for (OnlineUser onlineUser : onlineUsers) {
            CacheUtils.remove(CacheUtils.AUTHENTICATION_CACHE, onlineUser.getToken());
            onlineUserService.removeById(onlineUser.getId());
        }
    }

    @Override
    public void resetPwd(String id) {
        User user = this.getById(id);
        if (user == null){
            throw new BusinessException("数据异常");
        }
        this.update(Wrappers.<User>update().eq("id",id).set("password", Md5Utils.md5(userDefaultPassword)));

        // 踢出所有在线用户
        List<OnlineUser> onlineUsers = onlineUserService.list(Wrappers.<OnlineUser>query().eq("user_code", user.getCode()));
        for (OnlineUser onlineUser : onlineUsers) {
            CacheUtils.remove(CacheUtils.AUTHENTICATION_CACHE, onlineUser.getToken());
            onlineUserService.removeById(onlineUser.getId());
        }
    }

    @Override
    public Map uploadPicture(MultipartFile file, String userId) {
        String userSavePath = resPath + Const.USER_IMAGE_SUBPATH;
        String userThumbnailSavePath = resPath + Const.USER_IMAGE_THUMBNAIL_SUBPATH;
        String fileName = userId + "." + getFileType(file.getOriginalFilename());
        try {
            File dir = new File(userSavePath);
            if (!dir.exists()) { // 判断目录是否存在
                dir.mkdirs();  //多层目录需要调用mkdirs
            }
            BufferedOutputStream out = new BufferedOutputStream(
                    new FileOutputStream(new File(userSavePath + fileName)));
            out.write(file.getBytes());
            out.flush();
            out.close();
            ImageUtils.resize(userSavePath + fileName, userThumbnailSavePath + fileName,
                    Const.USER_IMAGE_THUMBNAIL_WIDTH, 0, 0, 0);
        } catch (Exception ex) {
            throw new BusinessException(ex.getMessage());
        }
        // 返回原图，缩略图的链接
        String vPath = Const.USER_IMAGE_VPATH + fileName;
        String vPathThumbnail = Const.USER_IMAGE_THUMBNAIL_VPATH + fileName;
        this.update(Wrappers.<User>update()
                .set("avatar_url", vPath)
                .set("thumbnail_image_url", vPathThumbnail)
                .eq("id", userId));
        Map map = new HashMap<String, String>();
        map.put("url", vPath);
        map.put("thumbnailUrl", vPathThumbnail);
        return map;
    }

    @Override
    public List<User> listByRole(String roleId) {
        List<RoleUser> roleUsers = roleUserService.getByRoleId(roleId);
        if (roleUsers.isEmpty()){
            return null;
        }
        List<String> userIds = roleUsers.stream().map(o -> o.getUserId()).collect(Collectors.toList());
        List<User> list = this.list(Wrappers.<User>query().in("id", userIds));
        return list;
    }

    private String getFileType(String fileName) {
        Integer start = fileName.lastIndexOf(".");
        if (start > -1) {
            return fileName.substring(start + 1);
        } else {
            return "";
        }
    }

    @Override
    public boolean saveOrUpdate(User entity) {
        // 新增
        if (StringUtils.isBlank(entity.getId())){
            // 默认密码
            String md5 = Md5Utils.md5(userDefaultPassword);
            entity.setPassword(md5);
            // 登录账号唯一性
            User user = findByCode(entity.getCode());
            if (user != null){
                throw new BusinessException("登录账号已存在");
            }
            // 工号唯一性
            int count = this.count(Wrappers.<User>query()
                    .eq("work_no", entity.getWorkNo()));
            if (count > 0 ) {
                throw new BusinessException(String.format("不允许重复工号，查询，目前存在%s个", count));
            }
        } else {
            // 工号唯一性
            int count = this.count(Wrappers.<User>query()
                    .eq("work_no", entity.getWorkNo()).ne("id", entity.getId()));
            if (count > 0 ) {
                throw new BusinessException(String.format("不允许重复工号，查询，目前存在%s个", count));
            }
        }

        Dept dept = deptService.getById(entity.getOfficeId());

        boolean sucuss = super.saveOrUpdate(entity);
        UserWarehouse user = userWarehouseService
                .getOne(Wrappers.<UserWarehouse>query().eq("warehouse_id", dept.getWarehouseId()).eq("user_id", entity.getId()));
        if (user==null && dept.getWarehouseId() != null){
            UserWarehouse userWarehouse = new UserWarehouse();
            userWarehouse.setDeptId(dept.getId())
                    .setUserId(entity.getId()).setType("user")
                    .setWarehouseId(dept.getWarehouseId());
            userWarehouseService.save(userWarehouse);

        }

        return sucuss;
    }

    @Override
    public User findByWorkNo(String WorkNo) {
        return this.getOne(
                Wrappers.<User>query().eq("work_no", WorkNo)
        );
    }


    @Override
    /**
    * @Description：更新用户操作状态
    */
    public void updateUserState(String type, String userId, String stateKey, String stateValue) {
        synchronized (userId.intern()){
            String stateFieldName = "state_pc";
            User user =  this.getById(userId);
            String stateDb = user.getStatePc();
            if (type.equals("app")) {
                stateFieldName = "state_app";
                stateDb=user.getStateApp();
            }
            JSONObject stateOb = JSONObject.parseObject(stateDb);
            if (stateOb ==null ) stateOb = new JSONObject();
            stateOb.put(stateKey,stateValue);
            this.update(Wrappers.<User>update().eq("id",userId).set(stateFieldName,stateOb));
        }
    }



    /**@Description： 导入模板字段标题
     */
    @Override
    protected List<ExcelImportField> excelGetFieldHead() {
        List<ExcelImportField> exportHeadList = new ArrayList<ExcelImportField>();
        ExcelImportField headDTO;

        headDTO = new ExcelImportField();
        headDTO.setName("name").setText("姓名").setCellIndex(0).setRequired(true).setMaxLength(50);
        exportHeadList.add(headDTO);

        headDTO = new ExcelImportField();
        headDTO.setName("workNo").setText("工号").setCellIndex(1).setRequired(true).setMaxLength(20).setUnq(true);
        exportHeadList.add(headDTO);

        headDTO = new ExcelImportField();
        headDTO.setName("officeInnerCode").setText("机构编码").setCellIndex(2).setRequired(true).setMaxLength(20);
        exportHeadList.add(headDTO);

        headDTO = new ExcelImportField();
        headDTO.setName("mobile").setText("手机号").setCellIndex(3).setRequired(false).setRequiredLength(11);
        exportHeadList.add(headDTO);
        return  exportHeadList;
    }


    // 导入数据 到数据库中
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String excelImportToDb(Map<String, String> reqData, List<User> itemImportList) {
        String roleCodeEveryone = "everyone";
        Role roleEveryone = roleService.getOne(Wrappers.<Role>query().eq("code",roleCodeEveryone), false);
        // 用户角色
        if (roleEveryone == null) {
            BusinessException.throwValidFail("未找到默认角色,请联系管理员，角色编码[%s]", roleCodeEveryone);
        }
        List<String> roleIdList = new ArrayList<>();
        roleIdList.add(roleEveryone.getId());


        // 部门集合，根据部门编码分组
        Map<String,Office> officeMapByWorkNo = officeService.list(Wrappers.<Office>query().isNotNull("inner_code"))
                .stream().collect(Collectors.toMap(Office::getInnerCode, item -> item));
        ;

        // 用户列表，根据工号分组
        Map<String,User> userGroupByWorkNo = this.list(Wrappers.<User>query().isNotNull("work_no"))
                .stream().collect(Collectors.toMap(User::getWorkNo, item -> item));
        ;

        // 用户列表，根据工号分组
        Map<String,User> userGroupByCode = this.list(Wrappers.<User>query().isNotNull("code"))
                .stream().collect(Collectors.toMap(User::getCode, item -> item));
        ;

        // 新增的用户
        List<User> dataListAdd = new ArrayList<>();

        String remark = StrUtil.format(
                "用户[{}] {} 导入",
                UserUtils.currentUser().getName(),
                DateUtils.format(DateUtils.nowDate(DateUtils.YYYYMMDDHHMMSS_PATTER2),DateUtils.YYYYMMDDHHMMSS_PATTER2)
        );
        int rowNum = 0;
        RoleUser roleUser ;
        List<RoleUser> roleUserList = new ArrayList<>();

        Set<String> deptCodeNotExist = new HashSet<>();
        int errNum = 0;
        for(User item  : itemImportList) {
            rowNum++;

            // 已存在
            if (!userGroupByWorkNo.containsKey(item.getWorkNo()) && !userGroupByCode.containsKey(item.getWorkNo())) {
                if (!officeMapByWorkNo.containsKey(item.getOfficeInnerCode())) {
                    deptCodeNotExist.add(item.getOfficeInnerCode());
                    errNum++;
                    continue;
                }

                item.setOfficeId(officeMapByWorkNo.get(item.getOfficeInnerCode()).getId())
                        .setCode(item.getWorkNo())
                        .setPassword(userDefaultPassword)
                        .setRemark(remark)
                ;
                item.setId(IdWorker.getIdStr());
                dataListAdd.add(item);

                for(String roleId: roleIdList) {
                    roleUser = new RoleUser();
                    roleUser.setRoleId(roleId);
                    roleUser.setUserId(item.getId());
                    roleUserList.add(roleUser);
                }
            }
        }

        if (!dataListAdd.isEmpty()) {
            roleUserService.saveBatch(roleUserList);

            this.saveBatch(dataListAdd);
        }

        if (ObjectUtil.length(deptCodeNotExist) == 0) {
            return StrUtil.format("新增用户数量:{}",dataListAdd.size());
        }
        return StrUtil.format("新增用户数量:{},失败数量:{},以下机构不存在:{}",dataListAdd.size(),errNum,StringUtils.join(deptCodeNotExist,","));
    }

}
